/*
 * CHttpService.cpp
 *
 *  Created on: 2018年1月8日
 *      Author: terry
 */

#include "CHttpService.h"
#include "CHttpServer.h"
#include "Path.h"
#include "AppConst.h"
#include "HttpUtil.h"
#include "DateTime.h"

#include "TStringUtil.h"
#include "Version.h"
#include "AppConfig.h"

#include "Application.h"
#include "DbHttpUserStore.h"
#include "Utility.h"
#include "CDataStore.h"
#include "TStringJoiner.h"


CHttpService::CHttpService():
    m_server(new CHttpServer()),
	m_port()
{
	setupUri();
}

CHttpService::~CHttpService()
{
    stop();
}

void CHttpService::setDb(std::shared_ptr< DataStore >& dbStore)
{
	m_dbStore = dbStore;

	std::shared_ptr< DbHttpUserStore > store(new DbHttpUserStore(dbStore));

	setUserStore(HttpUserStorePtr(store));
}

bool CHttpService::addStatic(const std::string& filedir, const std::string& urlPath)
{
	if (filedir.empty() || urlPath.empty())
	{
		return false;
	}

	if (!comn::StringUtil::startsWith(urlPath, "/"))
	{
		return false;
	}

	m_staticMap[filedir] = urlPath;
	return true;
}

bool CHttpService::start(int port, const std::string& webroot)
{
    if (isStarted())
    {
        stop();
    }

	m_port = port;

    std::string webrootPath = webroot;
    if (webrootPath.empty())
    {
    	webrootPath = getWebsitePath();
    }
    m_server->setWebRoot(webrootPath.c_str());

    m_server->setUserFile(NULL);

	std::string uploadPath = getUploadPath();
	comn::Path::createDirectories(uploadPath);
    m_server->setUploadDir(uploadPath.c_str());

    //m_server->addDir("/storage", AppConst::getRecordDir().c_str());
	StringMap::const_iterator it = m_staticMap.begin();
	for (; it != m_staticMap.end(); ++it)
	{
		m_server->addDir(it->second.c_str(), it->first.c_str());
	}

	m_server->addJsonApi("/api/");
	m_server->addJsonApi("/system/");

    return m_server->start(port, this);
}

void CHttpService::stop()
{
    m_server->stop();
}

bool CHttpService::isStarted()
{
    return m_server->isStarted();
}

void CHttpService::broadcast(void *data, size_t len)
{
    m_server->broadcast(data, len);
}

void CHttpService::broadcast(const std::string& data)
{
	m_server->broadcast(const_cast<char*>(data.c_str()), data.size());
}

void CHttpService::handleUpload(const char* name, const std::string& filepath)
{
	m_uploadFilePath = filepath;
}

void CHttpService::setupUri()
{
	setup("/api/login", std::bind(&CHttpService::login, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/logout", std::bind(&CHttpService::logout, this, std::placeholders::_1, std::placeholders::_2));

	setup("/api/version", std::bind(&CHttpService::getVersion, this, std::placeholders::_1, std::placeholders::_2));

	setup("/api/user", HttpMethod::GET, std::bind(&CHttpService::searchUser, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/user", HttpMethod::POST, std::bind(&CHttpService::addUser, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/user/:id", HttpMethod::GET, std::bind(&CHttpService::getUser, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/user/:id", HttpMethod::PUT, std::bind(&CHttpService::updateUser, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/user/:id", HttpMethod::DELETE, std::bind(&CHttpService::delUser, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/user/:id/enabled", HttpMethod::PUT, std::bind(&CHttpService::enableUser, this, std::placeholders::_1, std::placeholders::_2));

	setup("/api/user/exist/:username", HttpMethod::GET, std::bind(&CHttpService::existUserName, this, std::placeholders::_1, std::placeholders::_2));

	setup("/api/user/:id/password", HttpMethod::PUT, std::bind(&CHttpService::setPassword, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/user/:id/password", HttpMethod::POST, std::bind(&CHttpService::changePassword, this, std::placeholders::_1, std::placeholders::_2));

	setup("/api/event", HttpMethod::GET, std::bind(&CHttpService::searchEvent, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/event/:id", HttpMethod::GET, std::bind(&CHttpService::getEvent, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/event/:id", HttpMethod::DELETE, std::bind(&CHttpService::delEvent, this, std::placeholders::_1, std::placeholders::_2));

	setup("/api/image", HttpMethod::GET, std::bind(&CHttpService::searchImage, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/image/:id", HttpMethod::GET, std::bind(&CHttpService::getImage, this, std::placeholders::_1, std::placeholders::_2));
	setup("/api/image/:id", HttpMethod::DELETE, std::bind(&CHttpService::delImage, this, std::placeholders::_1, std::placeholders::_2));

}

int CHttpService::handleFileApi(HttpRequest& request,
        std::string& resp, std::string& mime)
{
    return 0;
}

void CHttpService::handleWebSocketFrame(unsigned char *data, size_t size, unsigned char flags)
{
    // pass
}

std::string CHttpService::getWebsitePath()
{
	std::string dir = comn::Path::getWorkDir();
	std::string fullpath = comn::Path::join(dir, "webroot");
	if (!comn::Path::exist(fullpath))
	{
		fullpath = "webroot";
	}
	return fullpath;
}

std::string CHttpService::getUserPath()
{
    std::string dir = comn::Path::getWorkDir();
	return comn::Path::join(dir, "user.htpasswd");
}

std::string CHttpService::getUploadPath()
{
	std::string dir = comn::Path::getWorkDir();
	return comn::Path::join(dir, "upload");
}

bool CHttpService::parseJson(const mg_str& str, Json::Value& json)
{
	Json::Reader reader;
	std::string text(str.p, str.len);
	return reader.parse(text, json);
}

int CHttpService::reboot(HttpRequest& request, std::string& jsonText)
{
	std::string cmd = "reboot";
	system(cmd.c_str());

	setError(0, "", jsonText);

	return 0;
}

int CHttpService::reset(HttpRequest& request, std::string& jsonText)
{
	// todo

	setError(0, "", jsonText);

	return 0;
}

int CHttpService::upgrade(HttpRequest& request, std::string& jsonText)
{
	return 0;
}

int CHttpService::getVersion(HttpRequest& request, std::string& jsonText)
{
	Json::Value json;
	json["version"] = APP_VERSION;

	jsonText = json.toStyledString();

	return 0;
}


std::string CHttpService::makeUrl(const std::string& filepath, const std::string& dir, const std::string& webroot)
{
	std::string basename = filepath.substr(dir.length());
#ifdef WIN32
	comn::StringUtil::replace(basename, "\\", "/");
#endif //
	return webroot + basename;
}

std::string CHttpService::getHostIp()
{
	std::string ip = Utility::getHostIp();
	return ip;
}


int CHttpService::searchUser(HttpRequest& request, std::string& jsonText)
{
	int offset = 0;
	request.get("offset", offset);
	int limit = AppConst::PAGE_SIZE;
	request.get("limit", limit);

	std::string name;
	request.get("name", name);

	std::string sql = "select user_info.*, user.username from user_info, user";
	std::string condition = "where user.id=user_info.id";

	if (!name.empty())
	{
		condition += comn::StringUtil::format(" and (user.id like '%%%s%%' or user.username like '%%%s%%')",
				name.c_str(), name.c_str());
	}

	int total = 0;
	JsonList jsonList;

	if (!m_dbStore->search("user_info,user", sql, condition, "", offset, limit, total, jsonList))
	{
	}

	jsonText = CDataStore::toJsonString(offset, limit, total, jsonList);

	return 0;
}

int CHttpService::addUser(HttpRequest& request, std::string& jsonText)
{
	Json::Value json;
	if (!parseJson(request.body, json))
	{
		return setError(EINVAL, "need body json", jsonText);
	}

	std::string id = json["id"].asString();
	if (id.empty())
	{
		id = json["username"].asString();
	}
	else if (hasUser(id))
	{
		return setError(EALREADY, "id already exist", jsonText);
	}

	std::string username = json["username"].asString();
	std::string password = json["password"].asString();
	std::string hash = password;

	if (hasUserName(username))
	{
		return setError(EALREADY, "username already exist", jsonText);
	}

	std::string sql = comn::StringUtil::format("insert into user values('%s', '%s', '%s', '%s')",
			id.c_str(), username.c_str(),
			password.c_str(), hash.c_str());
	if (!m_dbStore->execute(sql))
	{
		return setError(EIO, "failed", jsonText);
	}

	json["id"] = id;
	json["created"] = time(NULL);
	json["updated"] = time(NULL);

	json.removeMember("username");
	json.removeMember("password");

	if (!m_dbStore->insert("user_info", json))
	{
		sql = comn::StringUtil::format("delete from user where id='%s'", id.c_str());
		m_dbStore->execute(sql);

		return setError(EIO, "failed", jsonText);
	}

	jsonText = json.toStyledString();

	return 0;
}

int CHttpService::delUser(HttpRequest& request, std::string& jsonText)
{
	std::string id;
	if (!request.get("id", id))
	{
		return setError(EINVAL, "need id", jsonText);
	}

	std::string username;
	queryUserName(id, username);
	if (username == "admin")
	{
		return setError(EINVAL, "can not delete this user", jsonText);
	}

	std::string sql = comn::StringUtil::format("delete from user_info where id='%s'", id.c_str());
	if (!m_dbStore->execute(sql))
	{
		return setError(EIO, "failed", jsonText);
	}

	sql = comn::StringUtil::format("delete from user where id='%s'", id.c_str());
	m_dbStore->execute(sql);

	return 0;
}

int CHttpService::updateUser(HttpRequest& request, std::string& jsonText)
{
	std::string id;
	if (!request.get("id", id))
	{
		return setError(EINVAL, "need id", jsonText);
	}

	Json::Value json;
	if (!parseJson(request.body, json))
	{
		return setError(EINVAL, "need body json", jsonText);
	}

	json.removeMember("username");

	std::string condition = comn::StringUtil::format("where id='%s'", id.c_str());
	if (!m_dbStore->update("user_info", json, condition))
	{
		return setError(EIO, "failed", jsonText);
	}

	return 0;
}

int CHttpService::getUser(HttpRequest& request, std::string& jsonText)
{
	std::string id;
	if (!request.get("id", id))
	{
		return setError(EINVAL, "need id", jsonText);
	}

	std::string sql = comn::StringUtil::format(
			"select user_info.*, user.username from user_info, user where (user.id='%s' or user.username='%s') and user.id=user_info.id",
			id.c_str(), id.c_str());
	if (!m_dbStore->queryToJson(sql, jsonText))
	{
		return setError(ENOENT, "no such user", jsonText);
	}

	return 0;
}

int CHttpService::enableUser(HttpRequest& request, std::string& jsonText)
{
	std::string id;
	if (!request.get("id", id))
	{
		return setError(EINVAL, "need id", jsonText);
	}

	Json::Value json;
	if (!parseJson(request.body, json))
	{
		return setError(EINVAL, "need body json", jsonText);
	}

	bool enabled = json["enabled"].asBool();

	std::string sql = comn::StringUtil::format(
			"update user_info set enabled=%d where id='%s'",
			enabled,
			id.c_str());
	if (m_dbStore->update(sql) <= 0)
	{
		return setError(EIO, "failed", jsonText);
	}

	return 0;
}

int CHttpService::existUserName(HttpRequest& request, std::string& jsonText)
{
	std::string username;
	if (!request.get("username", username))
	{
		return setError(EINVAL, "need username", jsonText);
	}

	std::string sql = comn::StringUtil::format("select id, username from user where username='%s'", username.c_str());
	if (!m_dbStore->queryToJson(sql, jsonText))
	{
		jsonText = "{}";
	}

	return 0;
}

int CHttpService::setPassword(HttpRequest& request, std::string& jsonText)
{
	std::string id;
	if (!request.get("id", id))
	{
		return setError(EINVAL, "need id", jsonText);
	}

	Json::Value json;
	if (!parseJson(request.body, json))
	{
		return setError(EINVAL, "need body json", jsonText);
	}

	std::string password = json["password"].asString();

	std::string sql = comn::StringUtil::format("update user set password='%s', password_hash='%s' where id='%s'",
			password.c_str(), password.c_str(),
			id.c_str());
	if (!m_dbStore->execute(sql))
	{
		return setError(EIO, "failed", jsonText);
	}

	return 0;
}


int CHttpService::changePassword(HttpRequest& request, std::string& jsonText)
{
	std::string id;
	if (!request.get("id", id))
	{
		return setError(EINVAL, "need id", jsonText);
	}

	Json::Value json;
	if (!parseJson(request.body, json))
	{
		return setError(EINVAL, "need body json", jsonText);
	}

	std::string old = json["old"].asString();
	std::string password = json["password"].asString();

	std::string sql = comn::StringUtil::format("update user set password='%s', password_hash='%s' where id='%s' and `password`='%s'",
		password.c_str(), password.c_str(),
		id.c_str(), old.c_str());
	if (m_dbStore->update(sql) <= 0)
	{
		return setError(EIO, "failed", jsonText);
	}

	return 0;
}

bool CHttpService::hasUser(const std::string& id)
{
	Json::Value json;
	std::string sql = comn::StringUtil::format("select id, username from user where id='%s'", id.c_str());
	return m_dbStore->query(sql, json);
}

bool CHttpService::hasUserName(const std::string& username)
{
	Json::Value json;
	std::string sql = comn::StringUtil::format("select id, username from user where username='%s'", username.c_str());
	return m_dbStore->query(sql, json);
}

bool CHttpService::queryUserName(const std::string& id, std::string& username)
{
	std::string sql = comn::StringUtil::format("select username from user where id='%s'", id.c_str());
	return m_dbStore->query(sql, username);
}




int CHttpService::searchEvent(HttpRequest& request, std::string& jsonText)
{
	int offset = 0;
	request.get("offset", offset);
	int limit = AppConst::PAGE_SIZE;
	request.get("limit", limit);

	std::string company;
	request.get("company", company);

	std::string station;
	request.get("station", station);

	int num = 0;
	request.get("num", num);

	time_t fromTime = 0;
	time_t toTime = 0;
	request.get("start_time", fromTime);
	request.get("stop_time", toTime);

	std::string sql = "select * from event_record";

	comn::StringJoiner joiner("where ", " and ");
	if (!company.empty())
	{
		std::string condition = comn::StringUtil::format("company like '%%%s%%'", company.c_str());
		joiner.add(condition);
	}

	if (!station.empty())
	{
		std::string condition = comn::StringUtil::format("station like '%%%s%%'", station.c_str());
		joiner.add(condition);
	}

	if (num > 0)
	{
		joiner.add("num = ", num);
	}

	if (fromTime != 0)
	{
		joiner.add("created >= ", fromTime);
	}

	if (toTime != 0)
	{
		joiner.add("created < ", toTime);
	}

	int total = 0;
	JsonList jsonList;

	if (!m_dbStore->search("event_record", sql, joiner.str(), "created", offset, limit, total, jsonList))
	{
	}

	addUrl(jsonList);

	jsonText = CDataStore::toJsonString(offset, limit, total, jsonList);

	return 0;
}


int CHttpService::delEvent(HttpRequest& request, std::string& jsonText)
{
	int id = 0;
	if (!request.get("id", id))
	{
		return setError(EINVAL, "need id", jsonText);
	}

	std::string sql = comn::StringUtil::format("delete from event_record where id=%d", id);
	m_dbStore->execute(sql);

	return 0;
}


int CHttpService::getEvent(HttpRequest& request, std::string& jsonText)
{
	int id = 0;
	if (!request.get("id", id))
	{
		return setError(EINVAL, "need id", jsonText);
	}

	std::string sql = comn::StringUtil::format(
			"select * from event_record where id=%d", id);
	Json::Value json;
	if (!m_dbStore->query(sql, json))
	{
		return setError(ENOENT, "no such object", jsonText);
	}

	addUrl(json);
	jsonText = json.toStyledString();

	return 0;
}




int CHttpService::searchImage(HttpRequest& request, std::string& jsonText)
{
	int offset = 0;
	request.get("offset", offset);
	int limit = AppConst::PAGE_SIZE;
	request.get("limit", limit);

	std::string company;
	request.get("company", company);

	std::string station;
	request.get("station", station);

	int num = 0;
	request.get("num", num);

	time_t fromTime = 0;
	time_t toTime = 0;
	request.get("start_time", fromTime);
	request.get("stop_time", toTime);

	std::string sql = "select * from image_record";

	comn::StringJoiner joiner("where ", " and ");
	if (!company.empty())
	{
		std::string condition = comn::StringUtil::format("company like '%%%s%%'", company.c_str());
		joiner.add(condition);
	}

	if (!station.empty())
	{
		std::string condition = comn::StringUtil::format("station like '%%%s%%'", station.c_str());
		joiner.add(condition);
	}

	if (num > 0)
	{
		joiner.add("num = ", num);
	}

	if (fromTime != 0)
	{
		joiner.add("created >= ", fromTime);
	}

	if (toTime != 0)
	{
		joiner.add("created < ", toTime);
	}

	int total = 0;
	JsonList jsonList;

	if (!m_dbStore->search("image_record", sql, joiner.str(), "created", offset, limit, total, jsonList))
	{
	}

	addUrl(jsonList);

	jsonText = CDataStore::toJsonString(offset, limit, total, jsonList);

	return 0;
}


int CHttpService::delImage(HttpRequest& request, std::string& jsonText)
{
	int id = 0;
	if (!request.get("id", id))
	{
		return setError(EINVAL, "need id", jsonText);
	}

	std::string sql = comn::StringUtil::format("delete from image_record where id=%d", id);
	m_dbStore->execute(sql);

	return 0;
}


int CHttpService::getImage(HttpRequest& request, std::string& jsonText)
{
	int id = 0;
	if (!request.get("id", id))
	{
		return setError(EINVAL, "need id", jsonText);
	}

	std::string sql = comn::StringUtil::format(
			"select * from image_record where id=%d", id);
	Json::Value json;
	if (!m_dbStore->query(sql, json))
	{
		return setError(ENOENT, "no such object", jsonText);
	}

	addUrl(json);
	jsonText = json.toStyledString();

	return 0;
}

void CHttpService::addUrl(JsonList& jsonList)
{
	for (size_t i = 0; i < jsonList.size(); i ++)
	{
		Json::Value& value = jsonList[i];
		addUrl(value);
		
	}
}

void CHttpService::addUrl(Json::Value& json)
{
	std::string filepath = json["filepath"].asString();
	if (filepath.empty())
	{
		json["url"] = "";
	}
	else
	{
		std::string filedir;
		std::string urlPath;
		getUrlPathMap(filepath, filedir, urlPath);
		std::string url = makeUrl(filepath, filedir, urlPath);
		json["url"] = url;
	}
}

bool CHttpService::getUrlPathMap(const std::string& filepath, std::string& filedir, std::string& urlPath)
{
	StringMap::const_iterator it = m_staticMap.begin();
	for (; it != m_staticMap.end(); ++it)
	{
		if (matchDir(filepath, it->first))
		{
			filedir = it->first;
			urlPath = it->second;
			return true;
		}
	}

	filedir = comn::Path::dirname(filepath);
	urlPath = "/images";

	return true;
}

bool CHttpService::matchDir(const std::string& filepath, const std::string& filedir)
{
	bool matched = false;
	if (comn::StringUtil::startsWith(filepath, filedir))
	{
		char nextChar = '/';
		if (filepath.size() > filedir.size())
		{
			nextChar = filepath[filedir.size()];
		}
		matched = (nextChar == '/' || nextChar == '\\');
	}
	return matched;
}